﻿#region

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using Pob.CustomEventArgs;
using Pob.GameObjects.PowerUps.General;

#endregion

namespace Pob.GameObjects
{
    public delegate void PointScoredEvent(object sender, PointScoredEventArgs args);

    public class Ball : GameObject
    {
        #region Constants

        private const float Circle = MathHelper.Pi * 2;

        private const int MaxSpin = 17;

        #endregion

        #region Fields

        private readonly Settings _settings = SettingsManager.CurrentInstance.Settings;

        private bool _ballCentered;

        private float _drehung;

        private Circle _hitCircle;

        private float _kollisionWaitTime;

        private Player _lastTouched;

        private Vector2 _movement;

        private Vector2 _origin;

        private float _rotation;

        private float _speed;

        private float _spin;

        private float _spinToAdd;

        #endregion

        #region Constructors and Destructors

        public Ball(Game game, string assetName, Vector2 position, float sizeFactor, float speed, Vector2 movement, float spin, PointScoredEvent eventHandler)
            : base(game, assetName, position, sizeFactor, Color.White)
        {
            _speed = speed;
            _movement = Vector2.Normalize(movement);
            _spin = spin;
            _ballCentered = true;
            PointScored += eventHandler;
        }

        #endregion

        #region Public Events

        public event PointScoredEvent PointScored;

        #endregion

        #region Public Properties

        public float Speed
        {
            get
            {
                return _speed;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        ///     Prüft auf Kollsion mit einem anderen Gameobject.
        /// </summary>
        public bool CheckKollision(GameObject gameObject)
        {
            return _hitCircle.Intersects(gameObject.HitBox);
        }

        public bool CheckKollision(Ball ball2)
        {
            return _hitCircle.Intersects(ball2._hitCircle);
        }

        public bool CheckKollision(PowerUp powerUp)
        {
            if (powerUp != null)
            {
                return _hitCircle.Intersects(powerUp.HitCircle);
            }

            return false;
        }

        public override void Draw(GameTime gameTime)
        {
            SpriteBatch.Begin();
            SpriteBatch.Draw(Textur, Position, null, Color.White, _drehung, _origin, SizeFactor, SpriteEffects.None, 0f);
            SpriteBatch.End();
        }

        public void IncreaseSpeed(GameTime gameTime)
        {
            if (_speed < 600)
            {
                _speed += 50 * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
        }

        public override void Update(GameTime gameTime)
        {
            Position += _speed * _movement * (float)gameTime.ElapsedGameTime.TotalSeconds;

            _hitCircle = new Circle(Position, (Textur.Width / 2f) * SizeFactor);

            _rotation += _spin / 35;
            _rotation = _rotation % Circle;
            _drehung = _rotation;

            if (_kollisionWaitTime > 0)
            {
                _kollisionWaitTime -= gameTime.ElapsedGameTime.Milliseconds;
            }
            else
            {
                if (_spin < 0)
                {
                    if (_movement.X > 0)
                    {
                        _movement = Vector2.Normalize(new Vector2(_movement.X, _movement.Y + (_spin / 1200f)));
                    }
                    else
                    {
                        _movement = Vector2.Normalize(new Vector2(_movement.X, _movement.Y - (_spin / 1200f)));
                    }
                }

                if (_spin > 0)
                {
                    if (_movement.X > 0)
                    {
                        _movement = Vector2.Normalize(new Vector2(_movement.X, _movement.Y + (_spin / 1200f)));
                    }
                    else
                    {
                        _movement = Vector2.Normalize(new Vector2(_movement.X, _movement.Y - (_spin / 1200f)));
                    }
                }
            }

            // Spin wird jetzt in kleinen 1/64 Schritten aufgetragen, anstatt sofort komplett
            if (_spinToAdd != 0)
            {
                if (_spinToAdd < 0)
                {
                    _spin -= 0.015625f;
                    _spinToAdd += 0.015625f;
                }
                else
                {
                    _spin += 0.015625f;
                    _spinToAdd -= 0.015625f;
                }
            }

            if (_ballCentered)
            {
                KeyboardState kbState = Keyboard.GetState();

                _spin = 0;

                if (kbState.IsKeyDown(Keys.Space))
                {
                    _speed = _settings.StandardSpeed;
                    _ballCentered = false;
                }
            }

            if (_movement.X < 0.25f || _movement.X > -0.25f)
            {
                if (_movement.X < 0.25f && _movement.X > 0)
                {
                    _movement = Vector2.Normalize(new Vector2(0.25f, _movement.Y));
                }

                if (_movement.X > -0.25f && _movement.X < 0)
                {
                    _movement = Vector2.Normalize(new Vector2(-0.25f, _movement.Y));
                }
            }
        }

        /// <summary>
        ///     Reagiert auf vorhandene Kollsionen
        /// </summary>
        public void UpdateAfterKollision(GameObject gameObject)
        {
            if (gameObject is Ball)
            {
                UpdateAfterKollision((Ball)gameObject);
            }

            if (gameObject is Bande)
            {
                UpdateAfterKollision((Bande)gameObject);
            }

            if (gameObject is Paddle)
            {
                UpdateAfterKollision((Paddle)gameObject);
            }

            if (gameObject is PowerUp)
            {
                UpdateAfterKollision((PowerUp)gameObject);
            }

            if (gameObject is Endzone)
            {
                UpdateAfterKollision((Endzone)gameObject);
            }
        }

        #endregion

        #region Methods

        protected override void LoadContent()
        {
            SpriteBatch = new SpriteBatch(GraphicsDevice);
            Textur = Game.Content.Load<Texture2D>(AssetName);
            HitBox = new Rectangle((int)Position.X, (int)Position.Y, Textur.Width, Textur.Height);
            _origin = new Vector2(Textur.Width / 2f, Textur.Height / 2f);
            _hitCircle = new Circle(Position, (Textur.Width / 2f) * SizeFactor);
        }

        protected virtual void OnPointScored(bool finishingBlow, Player scorer)
        {
            var args = new PointScoredEventArgs(finishingBlow, scorer);

            if (PointScored != null)
            {
                PointScored(this, args);
            }
        }

        private void UpdateAfterKollision(PowerUp powerUp)
        {
            if (_lastTouched.StorePowerUp(powerUp))
            {
                powerUp.Store(_lastTouched);
            }
            else
            {
                Game.Components.Remove(powerUp);
            }
        }

        private void UpdateAfterKollision(Paddle paddle)
        {
            // Differenz zwischen Ball und Paddlemitte
            float difference = (paddle.Position.Y + (paddle.Textur.Height / 2f) - Position.Y) / 30;

            if ((difference > 1f) || (difference < -1f))
            {
                _movement = Vector2.Normalize(new Vector2(_movement.X, -difference));

                paddle.SlowMovement(this);
            }
            else
            {
                _movement = Vector2.Normalize(new Vector2(-_movement.X, -difference));

                Position = new Vector2(Position.X + (((5 * _settings.StandardSpeed) / _speed) * _movement.X), Position.Y);
            }

            KeyboardState keyBoardState = Keyboard.GetState();

            if (keyBoardState.IsKeyDown(paddle.Owner.KeyUp))
            {
                if (_movement.X > 0)
                {
                    if (_spin == 0)
                    {
                        _spinToAdd = 1;
                    }
                    else
                    {
                        _spinToAdd += 2;
                    }

                    if (_spin > MaxSpin)
                    {
                        _spin = MaxSpin;
                    }
                }

                if (_movement.X < 0)
                {
                    if (_spin == 0)
                    {
                        _spinToAdd = -1;
                    }
                    else
                    {
                        _spinToAdd -= 2;
                    }

                    if (_spin < -MaxSpin)
                    {
                        _spin = -MaxSpin;
                    }
                }
            }

            if (keyBoardState.IsKeyDown(paddle.Owner.KeyDown))
            {
                if (_movement.X < 0)
                {
                    if (_spin == 0)
                    {
                        _spinToAdd = 1;
                    }
                    else
                    {
                        _spinToAdd += 2;
                    }

                    if (_spin > MaxSpin)
                    {
                        _spin = MaxSpin;
                    }
                }

                if (_movement.X > 0)
                {
                    if (_spin == 0)
                    {
                        _spinToAdd = -1;
                    }
                    else
                    {
                        _spinToAdd -= 2;
                    }

                    if (_spin < -MaxSpin)
                    {
                        _spin = -MaxSpin;
                    }
                }
            }

            _kollisionWaitTime = (1000 / _speed) * 150;

            _lastTouched = paddle.Owner;
        }

        private void UpdateAfterKollision(Bande bande)
        {
            _spin -= bande.SpinAbschwächung;

            _movement = new Vector2(_movement.X, -_movement.Y);

            _kollisionWaitTime = (1000 / _speed) * 115;
        }

        private void UpdateAfterKollision(Ball ball2)
        {
            Vector2 newMovement = Vector2.Subtract(Position, ball2.Position);

            if (newMovement.X == 0)
            {
                newMovement = new Vector2(0.2f, newMovement.Y);
            }

            if (newMovement.Y < 0)
            {
                if (Position.Y < 0)
                {
                    _movement = Vector2.Normalize(newMovement);
                    Position += 2 * _movement;

                    ball2._movement = Vector2.Normalize(-newMovement);
                    ball2.Position += 2 * ball2._movement;
                }
                else
                {
                    _movement = Vector2.Normalize(-newMovement);
                    Position += 2 * _movement;

                    ball2._movement = Vector2.Normalize(newMovement);
                    ball2.Position += 2 * ball2._movement;
                }
            }
            else
            {
                if (Position.Y > 0)
                {
                    _movement = Vector2.Normalize(newMovement);
                    Position += 2 * _movement;

                    ball2._movement = Vector2.Normalize(-newMovement);
                    ball2.Position += 2 * ball2._movement;
                }
                else
                {
                    _movement = Vector2.Normalize(-newMovement);
                    Position += 2 * _movement;

                    ball2._movement = Vector2.Normalize(newMovement);
                    ball2.Position += 2 * ball2._movement;
                }
            }
        }

        private void UpdateAfterKollision(Endzone endzone)
        {
            Position = new Vector2(_settings.Width / 2f, (_settings.Height / 2f) + (_settings.Height / 20f));

            _movement = Vector2.Normalize(new Vector2(endzone.Position.X - (_settings.Width / 2f), 0));

            _ballCentered = true;

            _spin = 0;

            _speed = 0;

            OnPointScored(endzone.Owner.IncreaseScore(), endzone.Owner);
        }

        #endregion
    }
}